﻿//
// Created by LiuYou on 2021/4/12.
//

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/**
 * @brief 文件输入输出
 * C提供两种文件模式:
 *      文本模式
 *          在文本模式中，程序所见的内容和文件的实际内容不同。程序以文本模式读取文件时，把本地环境表示的行末尾或文件结尾映射为C模式。
 *          例如: 读取文件时将文件中的 \r 转换成 \n，写入文件时把 \n 转换成 \r; 或者把 \r\n 转换成 \n， 把 \n 转换成 \r\n。
 *      二进制模式
 *          在二进制模式中，程序可以访问文件的每个字节。
 *
 * 标准文件:
 *      C程序会自动打开3个文件，它们被称为standard input 、standard output、standard error output。
 *      在默认情况下，标准输入是系统的普通输入设备，通常为键盘; 标准输出和标准错误输出是系统的普通输出设备，通常为显示器。
 *      通常，标准输入为程序提供输入，它是getchar()和scanf()使用的文件。
 *      程序通常输出出到标准输出，它是putchar()、puts()和printf()使用的文件。
 *      这些IO函数的调用者可以看成是相应的标准文件。
 *
 *      TODO 注意: 我是这样理解。但是不知道正不正确。我觉得是正确的。不对是错误的。
 *      如标准输入文件调用getchar()和scanf()将从键盘输入的内容(该内容是在缓冲区中)读取到内存中。
 *      标准输出文件调用putchar()、puts()和printf()函数将内存中的内容(先写入到缓冲区中，然后再在)在显示器上输出。
 *      TODO 注意: 下面两行的理解我现在觉得是对的。而上面的理解一定不对。
 *      文件输入函数从缓冲区中读取数据放入文件指针fp指定的文件中。
 *      文件输出函数把fp指定的文件中的数据写入缓冲区，当缓冲区填满时，数据将被拷贝至标准输出文件中(即显示器上)。
 *
 * 标准I/O:
 *      * C语言提供了许多标准I/O函数处理不同的输入输出
 *      * 输入输出都是缓冲的，数据是先被拷贝到缓冲区，然后再做输入或者输出。
 *
 * exit()函数和return
 *      如果在一个递归的函数中, return只会结束该函数, 而exit()是直接结束程序运行。
 *
 * 指向标准文件的指针:
 *      stdin
 *      stdout
 *      stderr
 *      这些文件指针都是指向FILE的指针，所以它们可用作标准I/O函数的参数。
 *      例:
 *          fprintf(stderr, "Error in closing files.\n");
 *          fprintf()的第一个参数必须是一个文件指针。程序中使用stderr指针把错误消息发送到标准错误文件。
 *
 * 文件I/O函数
 *      fprintf()和fscanf()和printf()和scanf()类似，区别在于需要用第一个参数指定待处理的文件(需要处理的文件)。
 *
 * 随机访问文件: fseek()和ftell()
 *      fseek()函数可把文件看作是数组，在fopen()打开的文件中直接移动到任意字节处。
 *
 *      // 定位至文件开始处
 *      fseek(fp, 0L, SEEK_SET);
 *      // 定位至文件的第10个字节处
 *      fseek(fp, 10L, SEEK_SET);
 *      // 从文件当前位置向前移动2个字节
 *      // TODO 注意: 中间的参数必须是long类型的值，而且正是前移！前移！前移！前移！前移！前移！前移！前移！前移！前移！
 *      // TODO 注意: 负才是后移！后移！后移！后移！后移！后移！后移！后移！后移！后移！后移！后移！后移！后移！后移！后移！
 *      fseek(fp, 2L, SEEK_CUR);
 *      // 定位至文件结尾
 *      fseek(fp, 0L, SEEK_END);
 *      // 从文件末尾处回退10个字节
 *      fseek(fp, 10L, SEEK_END);
 *
 *      ftell()返回类型是long，它返回的是当前的位置。
 *      ftell()函数的返回值可以作为fseek()的第2个参数。
 *
 * fgetpos()和fsetpos()函数
 *      fseek()和ftell()潜在的问题是，它们都把文件限制在long类型能表示的范围内。
 *      但是随着存储设备容量迅猛增长，文件也越来越大。鉴于此，ANSI C新增了两个处理较大文件的新定位函数:
 *          fgetpos()
 *          fsetpos()
 *          这两个函数不使用long类型的值表示位置，它们使用一种新类型: fpos_t(file position type，文件定位类型)。
 *          fpos_t不是基本类型，
 *          int fgetpos(FILE* restrict stream, fpos_t* restrict pos);
 *          int fsetpos(FILE* restrict stream, const fpos_t* pos);
 *      应该用的不多。
 *
 * 由于stdio.h系列的所有输入函数都使用相同的缓冲区，所以调用任何一个函数都将从上一次函数停止调用的位置开始。
 * 所以fread()函数的文件位置指针是自动向后移动的。
 *
 * 二进制I/O:
 *      fread()和fwrite()
 *
 *      size_t fwrite(const void* restrict ptr, size_t size, size_t nmemb, FILE* restrict fp);
 *          把二进制数据写入文件
 *          size_t是sizeof运算符返回的类型，通常是unsigned int。返回值是成功写入项的数量，正常情况下，该返回值就是nmemb，但是出现问题返回值会比nmemb小。
 *          ptr是待写入数据块的地址。
 *          size表示待写入数据块的大小(以字节为单位)。
 *          nmemb表示待写入数据块的数量。
 *          fp指定待写入的文件。
 *          例:
 *              char buffer[256];
 *              memset(buffer, 0, sizeof(buffer));
 *              // 其实没有必要置为0
 *              // 初始化buffer...
 *              // 然后写入文件
 *              fwrite(buffer, 256, 1, fp);
 *
 *              double array[10];
 *              // ...
 *              fwrite(array, sizeof(double), 10, fp);
 *
 *      size_t fread(void* restrict ptr, size_t size, size_t nmemb, FILE* restrict fp);
 *          返回值为成功读取项的数量。正常情况下，该返回值就是nmemb，但是如果出现一些读取错误等，返回值就会比nmemb小。
 *          例:
 *              double doubleArray[10];
 *              fread(doubleArray, sizeof(double), 10, fp);
 *
 * int feof(FILE* fp);
 * int ferror(FILE* fp);
 * 如果标准输入函数返回EOF，则通常表明函数已经到达文件结尾。
 * 然而，出现读取错误时，函数也会返回EOF。feof()和ferror()函数用于区分这两种情况。
 * ferror()使用不多。
 *
 *
 * 如果要在不损失精度的前提下保存或恢复数值数据，请使用二进制模式以及fread()和fwrite()函数。
 * 标准I/O函数自动创建输入和输出缓冲区以加快数据传输。
 * fopen()函数做了很多事情！
 */
void fileInputOutput() {
    // 向文件写入内容
    char str[] = "I love C/C++ forever!";
    char* ptrStr = str;
    FILE* filePtr = fopen("Test", "w");
    if (filePtr == NULL) {
        fprintf(stderr, "打开失败！\n");
        exit(EXIT_FAILURE);
    }
    // =============================================
    // 一次写一个字节或者2个字节或者3个字节
    // 一次写入一个字节
/*    for (int i = 0; i < strlen(str); ++i) {
        fwrite(ptrStr, sizeof(char), 1, filePtr);
        ++ptrStr;
    }*/
    // 一次写入2个字节
    // 先 /
/*    for (int i = 0; i < strlen(str) / 2; ++i) {
        fwrite(ptrStr, sizeof(char), 2, filePtr);
        ptrStr += 2;
    }
    // 然后 %
    fwrite(ptrStr, sizeof(char), strlen(str) % 2, filePtr);*/
    // 一次写入2个字节的另一种写法
    for (int i = 0; i < strlen(str) / 2; ++i) {
        fwrite(ptrStr, 2 * sizeof(char), 1, filePtr);
        ptrStr += 2;
    }
    fwrite(ptrStr, sizeof(char), strlen(str) % 2, filePtr);
    // =============================================

    // +++++++++++++++++++++++++++++++++++++++++++++++++++++++
    // 一次性写入
//    fwrite(str, sizeof(char), strlen(str), filePtr);
    // +++++++++++++++++++++++++++++++++++++++++++++++++++++++

    fclose(filePtr);




    // 读取文件的内容
    filePtr = fopen("Test", "r");
    if (filePtr == NULL) {
        fprintf(stderr, "打开失败！\n");
        exit(EXIT_FAILURE);
    }

    // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    // 查看文件有多少个字节
    // 这个不行要使用ftell()函数
//    int fileSize = fseek(filePtr, 0, SEEK_END) - fseek(filePtr, 0, SEEK_SET);
    // TODO 注意: 非常重要！！！！
    fseek(filePtr, 0, SEEK_END);
    long fileSize = ftell(filePtr);
    printf("该文件含有%ld个字节。\n", fileSize);
    // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++


    // 不用重定位，因为使用fopen()打开文件文件位置指针默认指向第一个字节。但是上面统计了文件的大小(具体的字节数)
    fseek(filePtr, 0, SEEK_SET);
    char string[30];
    memset(string, 0, sizeof(string));
    char* buffer = string;

    // TODO 注意: 这里注意一下 使用 while (feof(fp) == 0)进行判断而不是EOF！！！！
//    if (feof(filePtr) != EOF) { // 错误！错误的原因是feof()函数的返回值是int。而且读取到EOF时才返回不等于0！
    while (feof(filePtr) == 0) { // 正确
//    while (filePtr) { // 注意: filePtr是无法进行判断的，因为它是一个FILE*类型。
        fread(buffer, sizeof(char), 1, filePtr);
        printf(" %c\n", *buffer);
        ++buffer;
    }
    printf("%s\n", string);
    fclose(filePtr);


}

int main() {
    fileInputOutput();


    return 0;
}